﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Net.Sockets;
using SilverlightClientLibrary.Messages;
using System.Windows.Threading;
using Vapor.Collections;

namespace SilverlightClientLibrary.Clients
{
    public class TcpSocketController
    {
        #region Private Members

        private Socket _socket;
        private SocketAsyncEventArgs _connectArgs;
        private SocketAsyncEventArgs _sendArgs;
        private SocketAsyncEventArgs _receiveArgs;
        private byte[] _readBuffer;
        private int _bytesReceived;
        private DispatcherTimer _dt;
        private bool _connecting;
        private LocklessQueue<byte[]> _buffersToSend;

        #endregion

        #region Events

        public event Action Connected;
        public event Action Dropped;
        public event Action DataSent;
        public event Action<byte[]> DataReceived;

        #endregion

        #region Properties

        public bool IsConnected { get { return _socket.Connected; } }
        public int EnqueuedBuffers { get { return _buffersToSend.Count; } }

        #endregion

        #region Constructors

        public TcpSocketController(Application app, int bufferSize, int port)
        {
            _readBuffer = new byte[bufferSize];
            _buffersToSend = new LocklessQueue<byte[]>();

            app.Exit += new EventHandler(App_Exit);

            DnsEndPoint rep = new DnsEndPoint("128.61.87.54", port, AddressFamily.InterNetwork);

            _connectArgs = new SocketAsyncEventArgs();
            _connectArgs.RemoteEndPoint = rep;
            _connectArgs.Completed += SocketConnected;
            _sendArgs = new SocketAsyncEventArgs();
            _sendArgs.RemoteEndPoint = rep;
            _sendArgs.Completed += SocketSent;
            _receiveArgs = new SocketAsyncEventArgs();
            _receiveArgs.RemoteEndPoint = rep;
            _receiveArgs.SetBuffer(new byte[bufferSize], 0, bufferSize);
            _receiveArgs.Completed += SocketReceived;

            _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            _socket.SendBufferSize = bufferSize * 5;

            _dt = new DispatcherTimer();
            _dt.Interval = TimeSpan.FromSeconds(3);
            _dt.Tick += new EventHandler(_dt_Tick);
        }

        #endregion

        #region Public Methods

        public void Connect()
        {
            if (!_dt.IsEnabled) _dt.Start();
            if (!_connecting)
            {
                _connecting = true;
                _socket.ConnectAsync(_connectArgs);
            }
        }

        public void EnqueueBuffer(byte[] buffer)
        {
            _buffersToSend.Enqueue(buffer);
        }

        //public void FlushBuffer()
        //{
        //    _buffersToSend.Enqueue(new byte[_readBuffer.Length]);
        //}

        #endregion

        #region Private Helpers

        private void App_Exit(object sender, EventArgs e)
        {
            _socket.Shutdown(SocketShutdown.Both);
            _socket.Dispose();
        }

        private void _dt_Tick(object sender, EventArgs e)
        {
            if (!_socket.Connected && !_connecting)
            {
                if (Dropped != null)
                {
                    Dropped();
                    Connect();
                }
            }
        }

        private void SendData()
        {
            byte[] buffer = null;
            while (!_buffersToSend.Dequeue(out buffer))
            {
                System.Threading.Thread.Sleep(1);
            }
            _sendArgs.SetBuffer(buffer, 0, buffer.Length);
            bool pending = _socket.SendAsync(_sendArgs);
            if (!pending && _socket.Connected) SocketSent(this, _sendArgs);
        }


        private void SocketConnected(object sender, SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success || e.SocketError == SocketError.IsConnected)
            {
                if(Connected != null) Connected();

                _receiveArgs.SetBuffer(_readBuffer, _bytesReceived, _readBuffer.Length - _bytesReceived);
                bool pending = _socket.ReceiveAsync(_receiveArgs);
                if (!pending) SocketReceived(this, _receiveArgs);

                SendData();
            }
            _connecting = false;
        }

        private void SocketReceived(object sender, SocketAsyncEventArgs e)
        {
            _bytesReceived += e.BytesTransferred;

            if (_bytesReceived == _readBuffer.Length)
            {
                _bytesReceived = 0;
                DataReceived(_readBuffer);
            }

            e.SetBuffer(_readBuffer, _bytesReceived, _readBuffer.Length - _bytesReceived);
            bool pending = _socket.ReceiveAsync(e);
            if (!pending && _socket.Connected) SocketReceived(this, e);
        }

        private void SocketSent(object sender, SocketAsyncEventArgs e)
        {
            if(DataSent != null) DataSent();
            SendData();
        }

        #endregion
    }
}
